package qq2564874169.tuaotuao.fx.app;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import qq2564874169.tuaotuao.fx.EventDispatcher;
import qq2564874169.tuaotuao.fx.FxStream;
import qq2564874169.tuaotuao.fx.io.StringReader;
import qq2564874169.tuaotuao.fx.utils.ClassUtil;
import qq2564874169.tuaotuao.fx.utils.ConvertUtil;
import qq2564874169.tuaotuao.fx.utils.StringUtil;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.*;

public class FxController {
    private static String firekey = UUID.randomUUID().toString().replace("-", "");
    public final EventDispatcher<ActionEventArgs> evActionBefore = new EventDispatcher<>(firekey);
    public final EventDispatcher<ActionEventArgs> evActionAfter = new EventDispatcher<>(firekey);
    public final EventDispatcher<ActionErrorEventArgs> evActionError = new EventDispatcher<>(firekey);
    private String body;

    void onActionBefore(ActionEventArgs args) {
        evActionBefore.fire(firekey, this, args);
    }

    void onActionAfter(ActionEventArgs args) {
        evActionAfter.fire(firekey, this, args);
    }

    void onActionError(ActionErrorEventArgs args) {
        evActionError.fire(firekey, this, args);
    }

    public RequestUser currentUser() {
        return RequestAuth.getUser(getRequest());
    }

    protected HttpServletRequest getRequest() {
        return ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
    }

    protected String getRequestBody() {
        if (body == null) {
            try {
                HttpServletRequest request = getRequest();
                if (request.getInputStream().isFinished()) {
                    Map<String, String[]> data = request.getParameterMap();
                    List<String> rs = new ArrayList<>();
                    for (String name : data.keySet()) {
                        String[] values = data.get(name);
                        for (String value : values) {
                            rs.add(name + "=" + value);
                        }
                    }
                    body = String.join("&", rs);
                } else {
                    body = StringReader.readToEnd(request.getInputStream());
                }
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
        return body;
    }

    protected boolean isAjax() {
        HttpServletRequest request = getRequest();
        if (StringUtil.isNullOrEmpty(request.getHeader("x-requested-with"))) {
            return false;
        }
        return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("x-requested-with"));
    }

    protected String getClientIp() {
        HttpServletRequest request = getRequest();
        String ip = null;
        String ipAddresses = request.getHeader("X-Forwarded-For");
        if (ipAddresses == null || ipAddresses.isEmpty() || "unknown".equalsIgnoreCase(ipAddresses)) {
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddresses == null || ipAddresses.isEmpty() || "unknown".equalsIgnoreCase(ipAddresses)) {
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ipAddresses == null || ipAddresses.isEmpty() || "unknown".equalsIgnoreCase(ipAddresses)) {
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ipAddresses == null || ipAddresses.isEmpty() || "unknown".equalsIgnoreCase(ipAddresses)) {
            ipAddresses = request.getHeader("X-Real-IP");
        }
        if (ipAddresses != null && !ipAddresses.isEmpty()) {
            ip = ipAddresses.split(",")[0];
        }
        if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ipAddresses)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    protected <T> T bindTo(Class<T> clazz) {
        try {
            T obj = ClassUtil.setNew(clazz);
            Map<String, Field> fields = FxStream.create(ClassUtil.fields(clazz, f -> f.getModifiers() == Modifier.PUBLIC)).toMap(i -> i.getName());
            if (fields.isEmpty()) {
                return obj;
            }
            HttpServletRequest request = getRequest();
            var names = FxStream.create(request.getParameterNames());
            for (String name : names.toList()) {
                Field f = fields.getOrDefault(name, null);
                if (f == null) {
                    continue;
                }
                f.trySetAccessible();
                Optional<String> value = getStringParam(f.getName());
                if (value.isPresent()) {
                    f.set(obj, ConvertUtil.change(value.get(), f.getType()));
                }
            }
            return obj;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    protected List<String> getParamKeys() {
        Enumeration<String> keys = getRequest().getParameterNames();
        return FxStream.create(keys).toList();
    }

    protected <T> Optional<T> getParam(String name, Class<T> clazz) {
        String value = getRequest().getParameter(name);
        if (StringUtil.hasValue(value)) {
            value = value.trim();
            T data = ConvertUtil.change(value, clazz);
            if (data == null) {
                return Optional.empty();
            }
            return Optional.of(data);
        }
        return Optional.empty();
    }

    protected Optional<Boolean> getBoolParam(String name) {
        return getParam(name, Boolean.class);
    }

    protected Optional<Integer> getIntParam(String name) {
        return getParam(name, Integer.class);
    }

    protected Optional<String> getStringParam(String name) {
        return getParam(name, String.class);
    }

    protected Optional<Long> getLongParam(String name) {
        return getParam(name, Long.class);
    }

    protected Optional<Date> getDateParam(String name) {
        return getParam(name, Date.class);
    }

    protected Optional<BigDecimal> getDecimalParam(String name) {
        return getParam(name, BigDecimal.class);
    }

}
